前幾天開始接觸 Android 的 Activity 之後,今天要把 Intent 再往前推一步。
Intent 不只是拿來「從 A 畫面跳到 B 畫面」,它還可以:
Bundle、putExtra() 傳送多筆資料ActivityResult 接收其他 Activity 的執行結果strings.xml 建立多國語系今天的練習主要可以分成三個部分:
Intent_3:城市圖片與資料傳遞
Intent_4:Activity 1 / Activity 2 雙向資料交換
Intent_5:飲料點餐 + Activity Result + 多國語系
在 Android 中,可以把 Intent 想成 Activity 之間傳遞的「信封」。
例如:
MainActivity
│
│ Intent
│ putExtra()
▼
SecondActivity
│
│ setResult()
▼
MainActivity
我們不只可以告訴 Android:
我要開啟哪一個 Activity。
還可以順便在 Intent 裡面放資料。
例如:
val intent = Intent(this, SecondActivity::class.java)
intent.putExtra("name", "Alex")
startActivity(intent)
到了 SecondActivity 再透過:
val name = intent.getStringExtra("name")
把資料取出來。
這就是今天所有範例最核心的概念。
參考資料:Android Developers — Intent
https://developer.android.com/guide/components/intents-filters
第一個練習是城市選擇。
MainActivity 裡面建立四個 ImageButton:
val btnTaipei: ImageButton = findViewById(R.id.btnTaipei)
val btnNewTaipei: ImageButton = findViewById(R.id.btnNewTaipei)
val btnTaoyuan: ImageButton = findViewById(R.id.btnTaoyuan)
val btnHsinchu: ImageButton = findViewById(R.id.btnHsinchu)
分別代表:
台北
新北
桃園
新竹
接著替每一個按鈕建立 setOnClickListener。
例如台北:
btnTaipei.setOnClickListener {
startDisplayActivity(
getString(R.string.taipei),
R.drawable.taipei,
R.string.taipei_desc
)
}
其他城市也是使用相同的方法:
btnNewTaipei.setOnClickListener {
startDisplayActivity(
getString(R.string.new_taipei),
R.drawable.newtaipei,
R.string.new_taipei_desc
)
}
btnTaoyuan.setOnClickListener {
startDisplayActivity(
getString(R.string.taoyuan),
R.drawable.taoyuan,
R.string.taoyuan_desc
)
}
btnHsinchu.setOnClickListener {
startDisplayActivity(
getString(R.string.hsinchu),
R.drawable.hsinchu,
R.string.hsinchu_desc
)
}
這裡沒有每一個按鈕都重新寫一次 Intent,而是整理成:
private fun startDisplayActivity(
cityName: String,
imageResId: Int,
descResId: Int
) {
val intent = Intent(this, DisplayActivity::class.java).apply {
// 傳送城市名稱
putExtra("CITY_NAME", cityName)
// 傳送圖片 Resource ID
putExtra("IMAGE_RES_ID", imageResId)
// 傳送城市說明文字 Resource ID
putExtra("DESC_RES_ID", descResId)
}
startActivity(intent)
}
這段程式一次傳送了三筆資料:
CITY_NAME
IMAGE_RES_ID
DESC_RES_ID
也就是:
MainActivity
│
│ CITY_NAME
│ IMAGE_RES_ID
│ DESC_RES_ID
▼
DisplayActivity
這也是函式封裝的好處。如果未來再增加「台中」、「台南」、「高雄」,不需要重複撰寫整段 Intent 程式。實際上傳的 MainActivity.kt 正是透過 startDisplayActivity() 統一將城市名稱、圖片 Resource ID 與說明 Resource ID 傳入 DisplayActivity。
參考資料:Android Developers — Intent
https://developer.android.com/reference/android/content/Intent
資料送出去之後,接下來就是要把它接回來。
在 DisplayActivity:
val cityName = intent.getStringExtra("CITY_NAME")
val imageResId = intent.getIntExtra(
"IMAGE_RES_ID",
0
)
val descriptionResId = intent.getIntExtra(
"DESC_RES_ID",
0
)
這裡要注意一件非常重要的事情:
傳送與接收使用的 Key 必須完全相同。
例如:
putExtra("CITY_NAME", ...)
↓
getStringExtra("CITY_NAME")
如果寫成:
getStringExtra("city_name")
就無法取得原本的資料。
取得圖片 Resource ID 後:
if (imageResId != 0) {
imageView.setImageResource(imageResId)
}
取得文字 Resource ID 後:
if (descriptionResId != 0) {
textView.setText(descriptionResId)
}
另外也將城市名稱設定到 ActionBar:
supportActionBar?.apply {
title = cityName
setDisplayHomeAsUpEnabled(true)
}
並加入返回功能:
override fun onSupportNavigateUp(): Boolean {
onBackPressedDispatcher.onBackPressed()
return true
}
因此這個 Activity 已經不是單純跳頁,而是會根據 MainActivity 傳入的資料,動態改變:
標題
圖片
說明文字
實際程式就是從 Intent 取得 CITY_NAME、IMAGE_RES_ID 與 DESC_RES_ID,再分別設定 ActionBar、ImageView 與 TextView。
參考資料:Android Developers — Intent.getStringExtra
https://developer.android.com/reference/android/content/Intent#getStringExtra(java.lang.String)
第二個練習開始更有意思。
程式架構變成:
┌──────────────┐
│ Activity_1 │
└──────┬───────┘
│
│ Result
▼
MainActivity ◄───────────────
│
│
▼
┌──────────────┐
│ Activity_2 │
└──────┬───────┘
│
│ Result
▼
MainActivity ◄───────────────
也就是 MainActivity 可以進入不同 Activity。
Activity 執行完成之後,又可以把結果傳回 MainActivity。
在 MainActivity 中首先定義:
private val Act1Code: Int = 100
private val ACT2_Code: Int = 200
接著:
private lateinit var getLaunch: ActivityResultLauncher<Intent>
然後在 onCreate() 中註冊:
getLaunch = registerForActivityResult(
ActivityResultContracts.StartActivityForResult(),
object : ActivityResultCallback<ActivityResult> {
override fun onActivityResult(result: ActivityResult) {
Log.d("main", "get callback")
val code = result.resultCode
Log.d("main", "code = $code")
if (code == Act1Code) {
val act1Intent = result.data
val data =
act1Intent?.getStringExtra("act1_data")
textViewData.text = data
}
if (code == ACT2_Code) {
val act2Intent = result.data
val data =
act2Intent?.getStringExtra("act2_data")
textViewData.text = data
}
}
}
)
這裡是今天非常重要的一段。
registerForActivityResult() 可以事先註冊:
當其他 Activity 回傳結果時,我要怎麼處理。
實際上傳的 Intent_4 MainActivity 使用 ActivityResultContracts.StartActivityForResult() 註冊 launcher,並依 resultCode 為 100 或 200 判斷結果來自哪一個 Activity。
參考資料:Android Developers — Activity Result API
https://developer.android.com/training/basics/intents/result
點擊第一個按鈕:
buttonAct1.setOnClickListener {
val data =
if (editTextInput1.text.isEmpty()) {
"no data"
} else {
editTextInput1.text.toString()
}
val intent =
Intent(
this@MainActivity,
Activity_1::class.java
)
intent.putExtra(
"data",
data
)
getLaunch.launch(intent)
}
流程:
EditText
│
▼
取得輸入文字
│
▼
Intent.putExtra("data", data)
│
▼
getLaunch.launch(intent)
│
▼
Activity_1
如果 EditText 沒有輸入資料:
"no data"
避免傳送空字串。
第二個 Activity 同樣如此:
buttonAct2.setOnClickListener {
val data =
if (editTextInput2.text.isEmpty()) {
"no data"
} else {
editTextInput2.text.toString()
}
val intent =
Intent(
this@MainActivity,
Activity_2::class.java
)
intent.putExtra(
"data",
data
)
getLaunch.launch(intent)
}
這兩個按鈕都使用同一個 ActivityResultLauncher 啟動不同 Activity,是這個範例值得注意的地方。
參考資料:Android Developers — Activity Result
https://developer.android.com/training/basics/intents/result
Activity_1 首先取得 MainActivity 傳過來的:
val data = intent.getStringExtra("data")
textViewData.text = data
接下來使用者可以在:
editTextInput
輸入新的資料。
按下返回按鈕:
buttonBack.setOnClickListener {
val resultIntent = Intent()
resultIntent.putExtra(
"act1_data",
editTextInput.text.toString()
)
setResult(
Act1Code,
resultIntent
)
finish()
}
流程變成:
MainActivity
│
│ "data"
▼
Activity_1
│
│ 使用者輸入資料
│
│ "act1_data"
▼
setResult(100, resultIntent)
│
▼
finish()
│
▼
MainActivity callback
這就是 Activity 之間的雙向資料傳遞。上傳的 Activity_1.kt 使用 "data" 接收 MainActivity 資料,再以 "act1_data" 搭配結果碼 100 回傳。
參考資料:Android Developers — setResult
https://developer.android.com/reference/android/app/Activity#setResult(int,%20android.content.Intent)
Activity_2:
val data = intent.getStringExtra("data")
textViewData.text = data
回傳時:
buttonBack.setOnClickListener {
val resultIntent = Intent()
resultIntent.putExtra(
"act2_data",
editTextInput.text.toString()
)
setResult(
ACT2_Code,
resultIntent
)
finish()
}
差異主要在:
Activity_1 → act1_data → ResultCode 100
Activity_2 → act2_data → ResultCode 200
因此 MainActivity 就能知道:
這筆資料到底是哪一個 Activity 回傳的?
實際 Activity_2.kt 也是相同結構,只是使用 "act2_data" 與結果碼 200。
參考資料:Android Developers — Activity
https://developer.android.com/reference/android/app/Activity
接下來把 Intent 應用到更接近實際 App 的情境:
飲料點餐系統。
MainActivity 顯示:
飲料:
甜度:
冰塊:
按下選擇後:
btnSelect.setOnClickListener {
val intent =
Intent(
this,
SecondActivity::class.java
)
startActivityForResult(
intent,
ACTCODE
)
}
接著進入 SecondActivity 選擇:
飲料名稱
甜度
冰塊
上傳的 MainActivity 目前實際使用的是傳統的 startActivityForResult(intent, ACTCODE);檔案中也保留了一段註解掉的新版 registerForActivityResult() 寫法,可以直接拿來比較兩種 Activity Result 寫法。
參考資料:Android Developers — Getting a result from an activity
https://developer.android.com/training/basics/intents/result
SecondActivity 中:
val etDrink =
findViewById<EditText>(
R.id.et_drink
)
val rgSugar =
findViewById<RadioGroup>(
R.id.rg_sugar
)
val rgIce =
findViewById<RadioGroup>(
R.id.rg_ice
)
val btnSend =
findViewById<Button>(
R.id.btn_send
)
按下送出:
btnSend.setOnClickListener {
val drink =
etDrink.text.toString()
val sugar =
findViewById<RadioButton>(
rgSugar.checkedRadioButtonId
).text.toString()
val ice =
findViewById<RadioButton>(
rgIce.checkedRadioButtonId
).text.toString()
val intent = Intent()
intent.putExtra(
"drink",
drink
)
intent.putExtra(
"sugar",
sugar
)
intent.putExtra(
"ice",
ice
)
setResult(
RESULT_OK,
intent
)
finish()
}
這次一次回傳三筆資料:
drink
sugar
ice
流程:
SecondActivity
EditText ──────► drink
RadioGroup ────► sugar
RadioGroup ────► ice
│
▼
Intent
│
putExtra × 3
│
▼
setResult(RESULT_OK)
│
▼
finish()
│
▼
MainActivity
實際 SecondActivity.kt 就是讀取 EditText 與兩組 RadioGroup 的選項,再用三個 Extra 回傳。
參考資料:Android Developers — RadioGroup
https://developer.android.com/reference/android/widget/RadioGroup
回到 MainActivity:
override fun onActivityResult(
requestCode: Int,
resultCode: Int,
data: Intent?
) {
super.onActivityResult(
requestCode,
resultCode,
data
)
if (
requestCode == ACTCODE &&
resultCode == RESULT_OK
) {
data?.let {
tvDrink.text =
getString(
R.string.drink_label,
it.getStringExtra("drink")
)
tvSugar.text =
getString(
R.string.sugar_label,
it.getStringExtra("sugar")
)
tvIce.text =
getString(
R.string.ice_label,
it.getStringExtra("ice")
)
}
}
}
這裡除了 Intent 之外,又出現今天另外一個重要觀念:
getString(
R.string.drink_label,
it.getStringExtra("drink")
)
而不是直接:
tvDrink.text =
"飲料:" + it.getStringExtra("drink")
為什麼?
因為這樣才能把顯示文字交給:
strings.xml
管理,替多國語系做好準備。上傳程式的確透過 getString(R.string.drink_label, ...)、sugar_label、ice_label 組合回傳內容。
參考資料:Android Developers — String resources
https://developer.android.com/guide/topics/resources/string-resource
今天 Android Studio 的畫面中,可以看到 Translations Editor。

這個工具可以直接管理不同語言的 String Resource。
畫面中可以看到像:
Key Default Value Chinese
app_name Intent_5 Intent_5
order Order drink 點餐:
select Select 選擇
drink Drink name 飲料名稱
sugar Sugar level 甜度:
ice Ice level 冰塊:
nosugar No sugar 無糖
littlesugar Little sugar 微糖
halfsugar Half sugar 半糖
fullsugar Full sugar 全糖
noice No ice 去冰
littleice Little ice 微冰
halfice Half ice 少冰
normalice Normal ice 正常冰
send Send 送出
confirm_order Confirm Order 確認餐點:
drink_label Drink: 飲料:
sugar_label Sugar: 甜度:
ice_label Ice: 冰塊:
這就是 Android Resource System 很方便的地方。
程式不用自己判斷:
if (language == "zh") {
text = "飲料"
} else {
text = "Drink"
}
而是直接寫:
android:text="@string/drink"
Android 就會依據裝置目前的語言,自動選擇適合的資源。
參考資料:Android Developers — Localize your app
https://developer.android.com/guide/topics/resources/localization
預設語系通常放:
res/
└── values/
└── strings.xml
例如:
<resources>
<string name="app_name">
Intent_5
</string>
<string name="order">
Order drink
</string>
<string name="select">
Select
</string>
<string name="drink">
Drink name
</string>
<string name="sugar">
Sugar level
</string>
<string name="ice">
Ice level
</string>
<string name="nosugar">
No sugar
</string>
<string name="littlesugar">
Little sugar
</string>
<string name="halfsugar">
Half sugar
</string>
<string name="fullsugar">
Full sugar
</string>
<string name="noice">
No ice
</string>
<string name="littleice">
Little ice
</string>
<string name="halfice">
Half ice
</string>
<string name="normalice">
Normal ice
</string>
<string name="send">
Send
</string>
</resources>
繁體中文則可以建立對應的語系資源目錄,例如:
res/
├── values/
│ └── strings.xml
│
└── values-zh-rTW/
└── strings.xml
繁體中文:
<resources>
<string name="app_name">
Intent_5
</string>
<string name="order">
點餐:
</string>
<string name="select">
選擇
</string>
<string name="drink">
飲料名稱
</string>
<string name="sugar">
甜度:
</string>
<string name="ice">
冰塊:
</string>
<string name="nosugar">
無糖
</string>
<string name="littlesugar">
微糖
</string>
<string name="halfsugar">
半糖
</string>
<string name="fullsugar">
全糖
</string>
<string name="noice">
去冰
</string>
<string name="littleice">
微冰
</string>
<string name="halfice">
少冰
</string>
<string name="normalice">
正常冰
</string>
<string name="send">
送出
</string>
</resources>
Android 會依照系統 Locale 自動尋找適合的 Resource。
主要練習:
Intent
putExtra()
getStringExtra()
getIntExtra()
主要練習:
Intent.putExtra()
registerForActivityResult()
ActivityResultLauncher
ActivityResultContracts.StartActivityForResult()
setResult()
主要練習:
Intent
startActivityForResult()
onActivityResult()
setResult()
EditText
RadioGroup
RadioButton
strings.xml

透過 Android Studio 的 Translations Editor,可以集中管理不同語言的 strings.xml。
例如:
| Key | English | 繁體中文 |
|---|---|---|
order |
Order drink | 點餐 |
select |
Select | 選擇 |
drink |
Drink name | 飲料名稱 |
sugar |
Sugar level | 甜度 |
ice |
Ice level | 冰塊 |
nosugar |
No sugar | 無糖 |
littlesugar |
Little sugar | 微糖 |
halfsugar |
Half sugar | 半糖 |
fullsugar |
Full sugar | 全糖 |
noice |
No ice | 去冰 |
littleice |
Little ice | 微冰 |
halfice |
Half ice | 少冰 |
normalice |
Normal ice | 正常冰 |
